Hop til hovedindhold

Custom Entra ID Role — EnergyConnect Infrastructure Deployer

Overview

Each EnergyConnect environment uses an Azure DevOps service connection to deploy infrastructure. These service connections are backed by Entra ID app registrations with federated credentials.

Rather than granting broad built-in roles (such as Directory Writers) or tenant-wide Graph API permissions (such as Group.ReadWrite.All), we use a custom Entra ID role that grants only the permissions required for infrastructure deployment.

Role name: EnergyConnect Infrastructure Deployer

Design Principles

  • No Graph API application permissions. All operations are handled through the custom directory role — no Application.ReadWrite.OwnedBy, AppRoleAssignment.ReadWrite.All, or Group.ReadWrite.All permissions are granted on the app registration.
  • One role per tenant. The role is defined once per Entra ID tenant and assigned to each environment's service connection.
  • createAsOwner for traceability. The role uses createAsOwner (rather than create) so that the service connection is automatically added as owner of resources it creates. This provides an audit trail of which service connection created each resource. Note: ownership is not required for subsequent updates — the update permissions are directory-scoped and apply to all resources of that type in the tenant.

What the Role Enables

The infrastructure deployment (Bicep with extension microsoftGraphV1) performs these Entra ID operations:

OperationBicep resource typeExample
Create app registrationsMicrosoft.Graph/applicationsClient auth app, AdvancedVEE app
Update app registration propertiesMicrosoft.Graph/applicationsApp roles, redirect URIs, scopes, audience
Create service principalsMicrosoft.Graph/servicePrincipalsEnterprise app for the app registration
Assign app roles to groupsMicrosoft.Graph/appRoleAssignedToGrant "MeteringPoint.Writer" role to the Administrator group
Create security groupsMicrosoft.Graph/groupsInfrastructure groups (Owner, Contributor, Reader, VM Admin), application groups (Administrator, CPMPHandler, etc.)

Permissions

App Registrations

PermissionWhy
applications/createAsOwnerCreate app registrations (client auth, AdvancedVEE). The SC is auto-added as owner for traceability.
applications/allProperties/readRead all properties. Required by the Graph extension to resolve existing apps by uniqueName and read back properties after creation. Narrower reads (standard/read + owners/read) are insufficient.
applications/basic/updateUpdate display name, description, homepage URL.
applications/appRoles/updateDefine app roles (e.g., MeteringPoint.Reader, ConnectionPoint.Writer).
applications/credentials/updateManage certificates and client secrets.
applications/authentication/updateConfigure redirect URIs, implicit flow settings, and sign-out URLs.
applications/permissions/updateConfigure required API permissions and exposed scopes (oauth2PermissionScopes).
applications/audience/updateSet the signInAudience property (used by the AdvancedVEE app registration).

Service Principals

PermissionWhy
servicePrincipals/createAsOwnerCreate the enterprise app (service principal) for each app registration.
servicePrincipals/allProperties/readRead all SP properties. Required by the Graph extension to resolve the SP and read its appRoles when creating appRoleAssignedTo resources. Narrower reads are insufficient.
servicePrincipals/basic/updateUpdate basic properties like display name and account enabled status.
servicePrincipals/appRoleAssignedTo/updateAssign app roles to Entra ID groups on the service principal (e.g., assign the "MeteringPoint.Writer" role to the "Administrator" group).

Security Groups

PermissionWhy
groups.security/createAsOwnerCreate security groups for infrastructure RBAC (Owner, Contributor, Reader, VM Admin) and application roles (Administrator, CPMPHandler, CPMPReader, SystemReader, Hangfire). Uses groups.security/ (not groups/) to limit creation to security groups only.
groups/standard/readRead standard group properties. Note: groups.security/standard/read does not exist in Entra ID custom roles — groups/standard/read covers all group types.
groups.security/basic/updateUpdate display name and description of security groups.
groups.security/members/updateManage security group membership.
groups.security/owners/updateManage security group ownership.

Security Considerations

Why createAsOwner?

Although ownership is not required for updates, createAsOwner is used for:

  1. Traceability — makes it clear which service connection created each resource
  2. Future-proofing — if Microsoft introduces ownership-scoped update permissions in the future, we're already positioned to use them
  3. No downside — functionally equivalent to create with the added benefit of auto-ownership

Management

Creating or Updating the Role

The role is managed by the Set-EntraCustomRole.ps1 script:

# Requires: Privileged Role Administrator or Global Administrator
az login --tenant <tenant-id>
./Infrastructure/Scripts/entra-id/Set-EntraCustomRole.ps1

Note: Role permission changes typically take effect immediately. If a deployment fails after a role update, verify the permissions are correct rather than assuming a propagation delay.

Assigning the Role

The role is automatically assigned to service connections during the bootstrap process (Initialize-ServiceConnection.ps1, step 6). It is assigned at the directory scope — this is required for create permissions to work.

Viewing Current Permissions

In the Entra ID portal: Roles and administrators → search for "EnergyConnect Infrastructure Deployer" → Permissions tab.

Or via CLI:

az rest --method GET `
--url 'https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions' `
--query "value[?displayName=='EnergyConnect Infrastructure Deployer'].rolePermissions[0].allowedResourceActions"